package protocol;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.TreeMap;

public class ConnectionDiscover implements OnGameStartListener, OnConnectionConfirmedListener {

	// Broadcast Ip
	private static final String BROADCAST_IP = "229.4.91.12";
	protected static final int MULTICAST_PORT = 8888;
	protected InetAddress broadcastIP;

	// Near rooms
	private TreeMap<String, GameToConnect> games;

	// Life cycle
	private boolean corriendo = true;

	// Game variables
	private String gameStyle;
	private RoomConnection room;

	// Broad Cast
	public String localIp;
	private MulticastSocket broadCastSocketToReceive;

	// Message listener
	private OnConnectionUpdatesListener connectionUpdatesListener;
	private OnNewMessageListener messageListener;

	public ConnectionDiscover(String gameSytyle, String myIp, OnConnectionUpdatesListener connectionUpdatesListener, OnNewMessageListener messageListener) {
		this.gameStyle = gameSytyle;
		this.localIp = myIp;
		this.connectionUpdatesListener = connectionUpdatesListener;
		this.messageListener = messageListener;
		this.games = new TreeMap<String, GameToConnect>();
		try {
			broadcastIP = InetAddress.getByName(BROADCAST_IP);
			broadCastSocketToReceive = new MulticastSocket(MULTICAST_PORT);
			broadCastSocketToReceive.joinGroup(broadcastIP);
		} catch (UnknownHostException e) {
			Print.print("UnknownHostException");
		} catch (SocketException e) {
			Print.print("SocketException");
		} catch (IOException e) {
			Print.print("SocketException");
		}
		gameScannerThread();
	}

	// *************************************** Game Scanner ***************************************

	/**
	 * This is the thread to listen what is happening outside. Catching the announced games.
	 */
	private void gameScannerThread() {

		// Time out thread
		new Thread(new Runnable() {

			@Override
			public void run() {

				while(corriendo) {
					try {Thread.sleep(1500);} catch (InterruptedException e) {}
					timeOutAvailableGames();
				}
			}
		}).start();

		// Listening thread
		new Thread(new Runnable() {

			@Override
			public void run() {

				while(corriendo) {
					try {
						byte[] buf = new byte[P.MIN_BYTES_BUFFER_SIZE];
						DatagramPacket paquete = new DatagramPacket(buf, buf.length);
						broadCastSocketToReceive.receive(paquete);
						String mensaje = new String(buf);

						// Checking if the initial structure is correct
						mensaje = XmlParser.readByTag(mensaje, P.ENCABEZADO, 0);
						if(mensaje != null) {

							// Checking the protocol version
							if(P.VERSION_CODE.equals(XmlParser.readByTag(mensaje, P.PROTOCOL_VERSION, 0))) {

								try {

									// Game name
									String gameName = XmlParser.readByTag(mensaje, P.GAME_NAME, 0);

									// Game name
									String gameStyle = XmlParser.readByTag(mensaje, P.GAME_STYLE, 0);

									// Port
									int portGame = Integer.valueOf(XmlParser.readByTag(mensaje, P.PORT, 0));

									// Checking in the game
									checkInGame(paquete.getAddress(), gameName, gameStyle, portGame);

								} catch(Exception e) {}

							}

						}

					} catch (IOException e) {
						Print.print("receiverThread-IOException");
					}
				}
			}
		}).start();
	}

	protected void checkInGame(InetAddress ip, String gameName, String gameStyle, int privatePort) {
		if(!ip.getHostAddress().equals(localIp) && this.gameStyle.equals(gameStyle)) {
			GameToConnect u = games.get(ip.getHostAddress());
			if(u == null) {
				u = new GameToConnect(ip.getHostAddress(), privatePort, gameName, gameStyle);
				games.put(ip.getHostAddress(), u);
				Print.print("new: "+u);
				connectionUpdatesListener.onGamesListUpdated(games.values());
			} else {
//				if(u.modify(ip, privatePort, gameName)) {
//					Print.print("modify: "+u);
//					connectionUpdatesListener.onGamesListUpdated(games.values());
//				}
			}
		}
	}

	/**
	 * Check out other games that stop announcing.
	 */
	private void timeOutAvailableGames() {
		try {
			long now = System.currentTimeMillis();
			boolean userGone = false;
			for (GameToConnect game : games.values()) {
				if(game.lastConnection(now) > 4000) {
					Print.print("Se fue "+game);
					games.remove(game.ip);
					userGone = true;
				}
			}
			if(userGone)
				connectionUpdatesListener.onGamesListUpdated(games.values());
		} catch(Exception e) {}
	}

	// *************************************** Game Scanner ***************************************

	// ************************************** Announcing Game *************************************

	public RoomConnection startAnnouncingGame(String gameName, String password) {
		try {
			if(room == null) {
				room = new RoomConnection(this, messageListener, this, gameStyle, gameName, password);
				if(messageListener != null)
					messageListener.getRoomConnection(room);
			}
			return room;
		} catch (IOException e) {
			return null;
		}
	}
	
	public boolean startGame() {
		if(room == null)
			return false;
		return room.startGame();
	}

	// ************************************** Announcing Game *************************************

	// ************************************** Connect to Game *************************************

	public RoomConnection connectToGame(GameToConnect game, String username, String password) {
		return connectToGame(game, username, password, true);
	}

	public RoomConnection connectToGame(GameToConnect game, String username, String password, boolean requestConnection) {

		try {
			room = new RoomConnection(this, messageListener, this, this, game.name, username, password, game.port, requestConnection);
			if(messageListener != null)
				messageListener.getRoomConnection(room);
			Print.print("Objeto de conexion creado");
		} catch(Exception e) {
			Print.print("Error al tratar de conectarse al juego: "+game.toString());
		}

		return room;
	}

	@Override
	public void onGameStart(long timeDifference, int countDown) {
		connectionUpdatesListener.onGameStart(timeDifference, countDown);
	}

	@Override
	public void onUsersListUpdated(Collection<User> users) {
		connectionUpdatesListener.onUsersListUpdated(users);
	}

	@Override
	public void onConnectionConfirmed(boolean confirmed) {
		if(!confirmed) {
			room = null;
		}
		connectionUpdatesListener.onConnectionConfirmed(confirmed);
		Print.print(confirmed ? "Confirmed!!!" : "Not confirmed!!!");
	}

	// ************************************** Connect to Game *************************************

}
